using System;
using System.Text;

namespace PDUConverter
{
    /// <summary>
    /// Provides the base for an SMS PDU.
    /// </summary>
    public abstract class SmsPdu : ITimestamp
    {
        private byte DCS;
        private const byte maxOctets = 140;
        private const int maxSeptets = 160;
        /// <summary>
        /// Gets the maximum message text length in septets.
        /// </summary>
        public const int MaxTextLength = 160;
        /// <summary>
        /// Gets the maximum Unicode message text length in characters.
        /// </summary>
        public const int MaxUnicodeTextLength = 70;

        private string _smscAddress;

        /// <summary>
        /// Initializes a new SmsPdu instance using default values.
        /// </summary>
        protected SmsPdu()
        {
            SmscAddress = string.Empty;
            ProtocolID = 0;
            DCS = 0;
            UserDataLength = 0;
            UserData = null;
        }

        /// <summary>
        /// Modifies an address to make it look like the specified address type.
        /// </summary>
        /// <param name="address">The address (phone number).</param>
        /// <param name="type">The address type.</param>
        /// <returns>The modified address.</returns>
        /// <remarks>If the address can't be modified, the original string is returned.</remarks>
        protected string CreateAddressOfType(string address, byte type)
        {
            if (address != string.Empty)
            {
                if ((type == 0x91) && !address.StartsWith("+"))
                {
                    return ("+" + address);
                }
                AddressType type2 = new AddressType(type);
                if (type2.Ton == 5)
                {
                    string s = BcdWorker.EncodeSemiOctets(address);
                    return TextDataConverter.SevenBitToString(TextDataConverter.OctetsToSeptetsStr(BcdWorker.GetBytes(s, 0, BcdWorker.CountBytes(s))), false);
                }
            }
            return address;
        }

        /// <summary>
        /// Decodes the text from 7-Bit user data.
        /// </summary>
        /// <returns>The decoded SmsPdu.</returns>
        /// <remarks>This method assumes that the <see cref="P:GsmComm.PduConverter.SmsPdu.UserData" /> property contains an encoded
        /// GSM 7-Bit default text packed into octets. If <see cref="P:GsmComm.PduConverter.SmsPdu.UserData" /> contains something different,
        /// the results are not defined.</remarks>
        protected string Decode7BitText()
        {
            string s = TextDataConverter.OctetsToSeptetsStr(this.UserData);
            int length = s.Length;
            return TextDataConverter.SevenBitToString(s, true);
        }

        /// <summary>
        /// Decodes an address out of a PDU string.
        /// </summary>
        /// <param name="pdu">The PDU string to use.</param>
        /// <param name="index">The index where to start in the string.</param>
        /// <param name="address">The address (phone number) read.</param>
        /// <param name="addressType">The address type of the read address.</param>
        protected static void DecodeGeneralAddress(string pdu, ref int index, out string address, out byte addressType)
        {
            byte @byte = BcdWorker.GetByte(pdu, index++);
            addressType = BcdWorker.GetByte(pdu, index++);
            if (@byte > 0)
            {
                bool flag = false;
                int length = (((@byte % 2) != 0) ? (@byte + 1) : @byte) / 2;
                if (((index * 2) + (length * 2)) > (pdu.Length - (index * 2)))
                {
                    length = (pdu.Length - (index * 2)) / 2;
                    flag = true;
                }
                string data = BcdWorker.GetBytesString(pdu, index, length);
                index += length;
                if (!flag)
                {
                    address = BcdWorker.DecodeSemiOctets(data).Substring(0, @byte);
                }
                else
                {
                    address = BcdWorker.DecodeSemiOctets(data).Substring(0, length * 2);
                }
            }
            else
            {
                address = string.Empty;
            }
        }

        /// <summary>
        /// Decodes an SMSC address out of a PDU string.
        /// </summary>
        /// <param name="pdu">The PDU string to use.</param>
        /// <param name="index">The index where to start in the string.</param>
        /// <param name="address">The address (phone number) read.</param>
        /// <param name="addressType">The address type of the read address.</param>
        protected static void DecodeSmscAddress(string pdu, ref int index, out string address, out byte addressType)
        {
            byte @byte = BcdWorker.GetByte(pdu, index++);
            if (@byte > 0)
            {
                byte num2 = BcdWorker.GetByte(pdu, index++);
                int length = @byte - 1;
                string data = BcdWorker.GetBytesString(pdu, index, length);
                index += length;
                string str2 = BcdWorker.DecodeSemiOctets(data);
                if (str2.EndsWith("F") || str2.EndsWith("f"))
                {
                    str2 = str2.Substring(0, str2.Length - 1);
                }
                addressType = num2;
                address = str2;
            }
            else
            {
                addressType = 0;
                address = string.Empty;
            }
        }

        /// <summary>
        /// Decodes the text from UCS2 (16-Bit) user data.
        /// </summary>
        /// <returns>The decoded SmsPdu.UserData.</returns>
        /// <remarks>This method assumes that the SmsPdu.UserData property contains an encoded
        /// UCS2 text. If SmsPdu.UserData contains something different, the results are not defined.
        /// </remarks>
        protected string DecodeUcs2Text()
        {
            return Encoding.BigEndianUnicode.GetString(UserData);
        }

        /// <summary>
        /// Gets the user data out of the string.
        /// </summary>
        /// <param name="pdu">The PDU string to use.</param>
        /// <param name="index">The index where to start in the string.</param>
        /// <param name="userDataLength">Receives the user data length in bytes.</param>
        /// <param name="userData">Received the user data.</param>
        /// <remarks>
        /// <para>If there's no data, userDataLength will be set to 0 and userData to null.</para>
        /// <para>Processing will abort at the first invalid character encountered or if the
        /// string ends too early, but will not change the userDataLength read from the string.</para>
        /// </remarks>
        protected static void DecodeUserData(string pdu, ref int index, out byte userDataLength, out byte[] userData)
        {
            byte @byte = BcdWorker.GetByte(pdu, index++);
            if (@byte <= 0)
            {
                userDataLength = 0;
                userData = null;
            }
            else
            {
                int length = BcdWorker.CountBytes(pdu) - index;
                string s = BcdWorker.GetBytesString(pdu, index, length);
                index += length;
                string str2 = string.Empty;
                for (int i = 0; i < (s.Length / 2); i++)
                {
                    string byteString = BcdWorker.GetByteString(s, i);
                    if (!Calc.IsHexString(byteString))
                    {
                        break;
                    }
                    str2 = str2 + byteString;
                }
                userDataLength = @byte;
                userData = Calc.HexToInt(str2);
            }
        }

        /// <summary>
        /// Encodes the specified text as 7-Bit user data.
        /// </summary>
        /// <param name="text">The text to encode.</param>
        /// <remarks>The text is converted to the GSM 7-Bit default alphabet first, then it is packed into octets.
        /// The final result is saved in the properties <see cref="P:GsmComm.PduConverter.SmsPdu.UserData" /> and <see cref="P:GsmComm.PduConverter.SmsPdu.UserDataLength" />.
        /// </remarks>
        /// <exception cref="T:System.ArgumentException">Text is too long.</exception>
        protected void Encode7BitText(string text)
        {
            string data = TextDataConverter.StringTo7Bit(text);
            int length = data.Length;
            if (length > 160)
            {
                string[] strArray = new string[] { "Text is too long. A maximum of ", 160.ToString(), " resulting septets is allowed. The current input results in ", length.ToString(), " septets." };
                throw new ArgumentException(string.Concat(strArray));
            }
            this.SetUserData(TextDataConverter.SeptetsToOctetsInt(data), (byte) length);
        }

        /// <summary>
        /// Encodes the specified text as UCS2 (16-Bit) user data.
        /// </summary>
        /// <param name="text">The text to encode.</param>
        /// <remarks>The text is converted to the UCS2 character set. The result is saved in the properties
        /// <see cref="P:GsmComm.PduConverter.SmsPdu.UserData" /> and <see cref="P:GsmComm.PduConverter.SmsPdu.UserDataLength" />.</remarks>
        protected void EncodeUcs2Text(string text)
        {
            byte[] bytes = Encoding.BigEndianUnicode.GetBytes(text);
            int length = bytes.Length;
            if (length > 140)
            {
                string[] strArray = new string[] { "Text is too long. A maximum of ", ((byte) 140).ToString(), " resulting octets is allowed. The current input results in ", length.ToString(), " octets." };
                throw new ArgumentException(string.Concat(strArray));
            }
            this.SetUserData(bytes, (byte) length);
        }

        /// <summary>
        /// Determines the address type.
        /// </summary>
        /// <param name="address">The address (phone number).</param>
        /// <param name="type">The detected address type.</param>
        /// <param name="useThisAddress">The modified address that can be directly used for communication.</param>
        /// <example>If you use address "+4812345678" the resulting type will be 0x91 and useThisAddress
        /// will be "4812345678". Call <see cref="M:GsmComm.PduConverter.SmsPdu.CreateAddressOfType(System.String,System.Byte)" /> to recreate the original address.</example>
        protected void FindTypeOfAddress(string address, out byte type, out string useThisAddress)
        {
            if (address != string.Empty)
            {
                byte num = 0x81;
                while (address.StartsWith("+"))
                {
                    num = 0x91;
                    address = address.Substring(1);
                }
                useThisAddress = address;
                type = num;
            }
            else
            {
                useThisAddress = address;
                type = 0;
            }
        }

        /// <summary>
        /// Modifies the message text so that it is safe to be sent via GSM 7-Bit default encoding.
        /// </summary>
        /// <param name="data">The message text.</param>
        /// <returns>The converted message text.</returns>
        /// <remarks>Replaces invalid characters in the text and truncates it to the maximum allowed length.</remarks>
        public static string GetSafeText(string data)
        {
            bool flag;
            bool flag2;
            return GetSafeText(data, out flag, out flag2);
        }

        /// <summary>
        /// Modifies the message text so that it is safe to be sent via GSM 7-Bit default encoding.
        /// </summary>
        /// <param name="data">The message text.</param>
        /// <param name="charsCorrected">Will be set to true if the message length was corrected.</param>
        /// <param name="lengthCorrected">Will be set to true if one or more characters were replaced.</param>
        /// <returns>The converted message text.</returns>
        /// <remarks>Replaces invalid characters in the text and truncates it to the maximum allowed length.</remarks>
        public static string GetSafeText(string data, out bool lengthCorrected, out bool charsCorrected)
        {
            string str;
            string str2;
            lengthCorrected = false;
            charsCorrected = false;
            if (data.Length > 160)
            {
                str = data.Substring(0, 160);
                lengthCorrected = true;
            }
            else
            {
                str = data;
            }
            do
            {
                bool flag;
                str2 = TextDataConverter.StringTo7Bit(str, false, out flag);
                if (flag)
                {
                    charsCorrected = true;
                }
                if (str2.Length > 160)
                {
                    str = data.Substring(0, str.Length - 1);
                    lengthCorrected = true;
                }
            }
            while (str2.Length > 160);
            return TextDataConverter.SevenBitToString(str2);
        }

        /// <summary>
        /// Gets the SMSC address and the type as it is saved internally.
        /// </summary>
        /// <param name="address">The SMSC address.</param>
        /// <param name="addressType">The address type of <b>address</b>.</param>
        public void GetSmscAddress(out string address, out byte addressType)
        {
            address = this._smscAddress;
            addressType = this.SmscAddressType;
        }

        /// <summary>
        /// Gets the length in septets of the specified text.
        /// </summary>
        /// <param name="text">The text the get the length for.</param>
        /// <returns>The text length.</returns>
        public static int GetTextLength(string text)
        {
            return TextDataConverter.StringTo7Bit(text).Length;
        }

        /// <summary>
        /// In derived classes, returns the relevant timestamp for the message.
        /// </summary>
        /// <returns>The timestamp.</returns>
        /// <remarks>If the message type does not have a relevant timestamp, it returns PduConverter.SmsTimestamp.None</remarks>
        public abstract SmsTimestamp GetTimestamp();
        /// <summary>
        /// Sets the SMSC address and type directly without attempting to
        /// autodetect the type.
        /// </summary>
        /// <param name="address">The SMSC address.</param>
        /// <param name="addressType">The address type of <b>address</b>.</param>
        public void SetSmscAddress(string address, byte addressType)
        {
            this._smscAddress = address;
            this.SmscAddressType = addressType;
        }

        /// <summary>
        /// Sets the user data using raw octets.
        /// </summary>
        /// <param name="data">The user data directly as a byte array.</param>
        /// <param name="dataLength">The length of the data. Note that this is not necessarily
        /// the number of bytes in the array, the length depends on the data coding.</param>
        /// <exception cref="T:System.ArgumentException">UserData is too long, more than 140 octets were passed</exception>
        /// <remarks>Assumes that raw octets are passed. Use the UserDataText property
        /// if you want to pass text.</remarks>
        public void SetUserData(byte[] data, byte dataLength)
        {
            if (data.Length > 140)
            {
                var strArray = new [] { "User data is too long. A maximum of ", ((byte) 140).ToString(), " octets is allowed. ", data.Length.ToString(), " octets were passed." };
                throw new ArgumentException(string.Concat(strArray));
            }
            UserData = data;
            UserDataLength = dataLength;
        }

        /// <summary>
        /// Converts the value of this instance into a string, including SMSC header.
        /// </summary>
        /// <returns>The encoded string.</returns>
        public override string ToString()
        {
            return ToString(false);
        }

        /// <summary>
        /// In derived classes, converts the value of this instance into a string.
        /// </summary>
        /// <param name="excludeSmscData">If true, excludes the SMSC header, otherwise it is included.</param>
        /// <returns>The encoded string.</returns>
        public abstract string ToString(bool excludeSmscData);

        /// <summary>
        /// Gets the length of the actual PDU data part in bytes. That is,
        /// without the SMSC header.
        /// </summary>
        public int ActualLength
        {
            get
            {
                return (ToString(true).Length / 2);
            }
        }

        /// <summary>
        /// Gets or sets the data coding scheme.
        /// </summary>
        /// <remarks><para>Represents the TP-DCS octet of the PDU.</para>
        /// <para>The data coding scheme specifies how the data is coded
        /// and may also specify a message class.</para></remarks>
        public byte DataCodingScheme
        {
            get
            {
                return DCS;
            }
            set
            {
                DCS = value;
            }
        }

        /// <summary>
        /// Gets or sets the protocol identifier.
        /// </summary>
        /// <remarks>Represents the TP-PID octet of the PDU.</remarks>
        public byte ProtocolID { get; set; }

        /// <summary>
        /// Gets or sets the SMSC address.
        /// </summary>
        /// <remarks>
        /// <para>When setting the property: Also the SmscAddressType property will be set,
        /// attempting to autodetect the address type.</para>
        /// <para>When getting the property: The address may be extended with address-type
        /// specific prefixes or other chraracters.</para>
        /// </remarks>
        public string SmscAddress
        {
            get
            {
                return CreateAddressOfType(_smscAddress, SmscAddressType);
            }
            set
            {
                byte num;
                string str;
                FindTypeOfAddress(value, out num, out str);
                _smscAddress = str;
                SmscAddressType = num;
            }
        }

        /// <summary>
        /// Gets the type of the SMSC address.
        /// </summary>
        /// <remarks>
        /// <para>Represents the Type-of-Address octets for the SMSC address of the PDU.</para>
        /// </remarks>
        public byte SmscAddressType { get; private set; }

        /// <summary>
        /// Gets the total length of the PDU string in bytes.
        /// </summary>
        public int TotalLength
        {
            get
            {
                return (ToString().Length / 2);
            }
        }

        /// <summary>
        /// Gets the user data.
        /// </summary>
        /// <remarks>
        /// <para>Represents the TP-User-Data octet of the PDU.</para>
        /// </remarks>
        public byte[] UserData { get; private set; }

        /// <summary>
        /// Gets or sets the user data length.
        /// </summary>
        /// <remarks>
        /// <para>Represents the TP-User-Data-Length octet of the PDU.</para>
        /// <para>The SmsPdu.UserDataLength does not necessarily match the number
        /// of bytes in the SmsPdu.UserData because it may be further encoded.</para>
        /// </remarks>
        public byte UserDataLength { get; private set; }

        /// <summary>
        /// Gets or sets the user data text (i.e. the message text).
        /// </summary>
        /// <remarks>
        /// <para>This property supports automatic encoding and decoding of text from and to the GSM 7-bit default
        /// alphabet and the UCS2 charset. For this to work properly, the SmsPdu.DataCodingScheme
        /// property must be set correctly before setting the UserDataText.</para>
        /// <para>For all other data with other alphabets or special data codings, the SmsPdu.UserData property
        /// must be used to get or set the data.</para>
        /// <para>Setting this property also sets the SmsPdu.UserDataLength property accordingly.</para>
        /// </remarks>
        public string UserDataText
        {
            get
            {
                byte alphabet;
                var scheme = PDUConverter.DataCodingScheme.Decode(DCS);
                if (scheme is GeneralDataCoding)
                {
                    var coding = (GeneralDataCoding) scheme;
                    alphabet = coding.Alphabet;
                }
                else
                    alphabet = 0;
                return alphabet == 2 ? DecodeUcs2Text() : Decode7BitText();
            }
            set
            {
                byte alphabet;
                var scheme = PDUConverter.DataCodingScheme.Decode(DCS);
                if (scheme is GeneralDataCoding)
                {
                    var coding = (GeneralDataCoding) scheme;
                    alphabet = coding.Alphabet;
                }
                else
                {
                    alphabet = 0;
                }
                if (alphabet == 2)
                {
                    EncodeUcs2Text(value);
                }
                else
                {
                    Encode7BitText(value);
                }
            }
        }
    }
}